<?php

/**
 * ===========================================
 * 模板引擎类
 * update: 2014-09-16
 * ============================================
 */

namespace PKFrame\Template;
defined('PATH_PK') or die();

use PKFrame\DataHandler\Arrays;
use PKFrame\DataHandler\Auth;

class Tpl
{
    private static $instance;
    // 用來保存所有的模板變量
    private $_tpl_param = [];
    //模板的主题
    private static $_tpl_themes = null;
    private $_tpl_dir = null;
    private $_tpl_isCommon = false;
    private $_is_admin, $_is_makeHtml;

    /**
     * 模板引擎初始化
     * @param $tagName
     * @return Tpl
     */
    public static function getInstance($tagName): Tpl
    {
        if (empty(self::$instance)) {
            self::$instance = array($tagName => new static);
        } elseif (!array_key_exists($tagName, self::$instance)) {
            self::$instance[$tagName] = new static();
        }
        return self::$instance[$tagName];
    }

    /**
     * 前台模板设置主题
     * @param $tpl_themes
     * @return $this
     */
    public function SetTplThemes($tpl_themes): Tpl
    {
        self::$_tpl_themes = $tpl_themes;
        return $this;
    }

    /**
     * 前台设置主题下的目录
     * @param $tpl_dir
     * @return $this
     */
    public function SetTplDir($tpl_dir = null): Tpl
    {
        $this->_tpl_dir = ((\request()->module() == 'Admin') || stristr(\request()->controller(), 'Admin')) ? ucfirst($tpl_dir) : $tpl_dir;
        return $this;
    }

    /**
     * @return null
     */
    private function _getTplDir()
    {
        return !empty($this->_tpl_dir) ? $this->_tpl_dir : \request()->module();
    }

    /**
     * 模板编译路径
     */
    private function _getCompiledPath(): string
    {
        $path = PATH_TMP . 'Templates' . DS;
        $path .= ($this->_is_admin && !$this->_is_makeHtml ? 'Admin' : self::$_tpl_themes) . DS;
        $path .= $this->_getTplDir() . DS;
        file_exists($path) ?: fileHelper()->MKDir($path);
        return $path;
    }

    /**
     * 模板路径
     * @param $fileName
     * @return string
     */
    private function _getTemplatePath($fileName = null): string
    {
        $this->_is_admin = in_array(request()->module(), ['Install', 'Admin']) || stristr(request()->controller(), 'Admin');
        $is_php = !is_null($fileName) && stristr($fileName, '.php');
        $this->_is_makeHtml = request()->controller() == 'AdminMakeHtml' && in_array(request()->action(), ['ApiByCreate', 'ApiByCreateSite']);
        if (($this->_is_admin && !$this->_is_makeHtml) || $is_php) {
            $tpl_path = path_app($this->_getTplDir()) . 'Template' . DS;
        } else {
            loader(__DIR__ . DS . 'TplTag');
            $tpl_path = PATH_ROOT . 'Templates' . DS . self::$_tpl_themes . DS . strtolower($this->_getTplDir()) . DS;
            $this->SetTplParam('template_style_path', '/Templates/' . self::$_tpl_themes . '/style');
        }
        !$this->_tpl_isCommon ?: $tpl_path = PATH_Frame . 'Template' . DS;
        return $tpl_path;
    }

    /**
     * 检查模板的文件名
     * @param $fileName
     * @return string
     */
    private function _checkFile($fileName): string
    {
        stristr($fileName, '.') ?: $fileName .= '.tpl';
        $path = $this->_getTemplatePath($fileName) . $fileName;
        try {
            if (!file_exists($path)) {
                throw new \Exception(language('TempLate_File_NoExists') . $path);
            }
        } catch (\Exception $e) {
            request()->isAjax()
                ? out()->noticeByJson($e->getMessage())
                : handlerException($e);
        }
        return $path;
    }

    /**
     * 检查文件中是否带有BOM
     * @param string $fileName 被检查的文件名
     */
    private function _checkBOM(string $fileName)
    {
        $contents = file_get_contents($fileName);
        $charset[1] = substr($contents, 0, 1);
        $charset[2] = substr($contents, 1, 1);
        $charset[3] = substr($contents, 2, 1);
        if (ord($charset[1]) == 239 && ord($charset[2]) == 187 && ord($charset[3]) == 191) {
            $fileNum = fopen($fileName, "w");
            flock($fileNum, LOCK_EX);
            fwrite($fileNum, substr($contents, 3));
            fclose($fileNum);
        }
    }

    private function _readFile($file)
    {
        // 检查模板文件的修改时间
        $templateFile = $this->_checkFile($file);
        $fileInfo = fileHelper()->BaseProperty($templateFile);
        $compiledFileName = $file . '.php';
        $compiledFilePath = $this->_getCompiledPath() . $compiledFileName;
        !file_exists($compiledFilePath) ?: $compiledFileInfo = fileHelper()->BaseProperty($compiledFilePath);
        // 释放模板变量
        if (count($this->_tpl_param) > 0) {
            extract($this->_tpl_param);
            extract(request()->param());
        }
        if (
            isset($compiledFileInfo) && is_array($fileInfo)
            && array_key_exists('c_time', $fileInfo) && array_key_exists('m_time', $fileInfo)
            && is_array($compiledFileInfo) && array_key_exists('m_time', $compiledFileInfo)
        ) {
            $isCreateCompiled = ($fileInfo['m_time'] > $compiledFileInfo['m_time'])
                || ($fileInfo['c_time'] > $compiledFileInfo['m_time']);
        } elseif (!isset($compiledFileInfo)) {
            $isCreateCompiled = true;
        } else {
            $isCreateCompiled = !file_exists($compiledFilePath);
        }
        // 判断是否要重新创建编译
        if ($isCreateCompiled) {
            ob_start();
            $this->_checkBOM($templateFile);
            require_once "{$templateFile}";
            $htmlCode_Template = ob_get_contents();
            $htmlCode_length = ob_get_length();
            ob_end_clean();
            fileHelper()->PutContents(
                $this->_getCompiledPath(),
                $compiledFileName,
                $this->_parseHandler($htmlCode_Template)
            );
        }
        if (file_exists($compiledFilePath)) {
            ob_start();
            /** @noinspection PhpIncludeInspection */
            require "{$compiledFilePath}";
            $htmlCode_Template = ob_get_contents();
            ob_end_clean();
            return $htmlCode_Template;
        } else {
            /* email me a failure notice! */
            $this->Notice('template compiled file:' . $compiledFilePath . ' no exists');
        }
    }

    public function PhpDisplay($file)
    {
        // 释放模板变量
        if (count($this->_tpl_param) > 0) {
            extract($this->_tpl_param);
        }
        $templateFile = $this->_checkFile($file . '.php');
        // 判断是否要重新创建编译
        ob_start();
        require "{$templateFile}";
        $htmlCode_Template = ob_get_contents();
        ob_end_clean();
        return $htmlCode_Template;
    }

    /**
     * 通知显示
     * @param $msg
     * @param null $url
     * @return false|string
     */
    public function Notice($msg, $url = null)
    {
        $this->_tpl_isCommon = true;
        $isLoadJS = !(stristr(\request()->controller(), 'Admin')) || (stristr(\request()->controller(), 'Public')
                || stristr(\request()->action(), 'Public'));
        $isLoadJS ?: $isLoadJS = !\request()->isAjax();
        $this->SetTplParam('msg', $msg);
        $this->SetTplParam('url', $url);
        $this->SetTplParam('isLoadJS', $isLoadJS);
        return $this->PhpDisplay('notice');
    }

    public function TplDisplay($file)
    {
        return $this->_readFile($file);
    }

    /**
     * 直接显示
     * @param $file
     * @return false|string
     */
    public function Display($file)
    {
        out()->html($this->TplDisplay($file));
    }

    /**
     * 解析模板
     * @param $str
     * @return string|string[]|null
     */
    private function _parseHandler($str)
    {
        $str = preg_replace("/\<{template\s+(.+)\}>/", "<?php echo tplTag_Template(\\1,'" . self::$_tpl_themes . "'); ?>", $str);
        $str = preg_replace("/\<{php\s+(.+)\}>/", "<?php \\1?>", $str);
        $str = preg_replace("/\<{if\s+(.+?)\}>/", "<?php if(\\1) { ?>", $str);
        $str = preg_replace("/\<{else\}>/", "<?php } else { ?>", $str);
        $str = preg_replace("/\<{elseif\s+(.+?)\}>/", "<?php } elseif (\\1) { ?>", $str);
        $str = preg_replace("/\<{\/if\}>/", "<?php } ?>", $str);
        //for 循环
        $str = preg_replace("/\<{for\s+(.+?)\}>/", "<?php for(\\1) { ?>", $str);
        $str = preg_replace("/\<{\/for\}>/", "<?php } ?>", $str);
        //++ --
        $str = preg_replace("/\<{\+\+(.+?)\}>/", "<?php ++\\1; ?>", $str);
        $str = preg_replace("/\<{\-\-(.+?)\}>/", "<?php ++\\1; ?>", $str);
        $str = preg_replace("/\<{(.+?)\+\+\}>/", "<?php \\1++; ?>", $str);
        $str = preg_replace("/\<{(.+?)\-\-\}>/", "<?php \\1--; ?>", $str);
        $str = preg_replace("/\<{loop\s+(\S+)\s+(\S+)\}>/", "<?php if(isset(\\1) && is_array(\\1)) foreach(\\1 AS \\2) { ?>", $str);
        $str = preg_replace("/\<{loop\s+(\S+)\s+(\S+)\s+(\S+)\}>/", "<?php if(isset(\\1) && is_array(\\1)) foreach(\\1 AS \\2 => \\3) { ?>", $str);
        $str = preg_replace("/\<{\/loop\}>/", "<?php } ?>", $str);
        $str = preg_replace("/\<{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}>/", "<?php echo \\1;?>", $str);
        $str = preg_replace("/\<{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}>/", "<?php if(isset(\\1)) {echo @\\1;}?>", $str);
        $str = preg_replace("/\<{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}>/", "<?php if(isset(\\1)) {echo \\1;}?>", $str);
        $str = preg_replace_callback("/\<{(\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\}>/s", array($this, 'addquote'), $str);
        $str = preg_replace("/\<{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}>/s", "<?php echo \\1;?>", $str);
        $str = preg_replace_callback("/\<{pk:(\w+)\s+([^}]+)\}>/i", array($this, 'pk_tag_callback'), $str);
        $str = preg_replace_callback("/\<{\/pk\}>/i", array($this, 'end_pk_tag'), $str);
        return $str;
    }

    /**
     * 转义 // 为 /
     * @param $matches
     * @return mixed
     */
    public function addquote($matches)
    {
        $var = '<?php echo ' . $matches[1] . ';?>';
        return str_replace("\\\"", "\"", preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var));
    }

    /**
     * @param $key
     * @param null $value
     * @param bool $isDecode
     * @return $this
     */
    public function SetTplParam($key, $value = null, $isDecode = true): Tpl
    {
        \request()->param($key, $value);
        if (is_array($value)) {
            $this->_tpl_param[$key] = $value;
        } else {
            $this->_tpl_param[$key] = $isDecode ? Auth::HtmlspecialcharsDeCode($value) : $value;
        }
        return $this;
    }

    public function SetTplParamList(array $lists = null): Tpl
    {
        if (!is_null($lists) && Arrays::Is($lists)) {
            \request()->param($lists);
            foreach ($lists as $index => $list) {
                $this->_tpl_param[$index] = Auth::HtmlspecialcharsDeCode($list);
            }
        }
        return $this;
    }

    public static function pk_tag_callback($matches): string
    {
        return self::pk_tag($matches[1], $matches[2], $matches[0]);;
    }

    /**
     * 解析PK标签
     * @param string $module 操作方式
     * @param string $data 参数
     * @param string $html 匹配到的所有的HTML代码
     * @return string
     */
    public static function pk_tag(string $module, string $data, string $html): string
    {
        //转换成参数数组
        preg_match_all("/([a-z]+)\=[\"]?([^\"]+)[\"]?/i", stripslashes($data), $matches, PREG_SET_ORDER);
        $param = array('module' => $module);
        //可视化条件
        foreach ($matches as $v) {
            $param[$v[1]] = $v[1] == 'action' ? ucwords($v[2]) : $v[2];
        }
        $display_html = '$res = self::tplTag_Module(' . str_replace("'", '"', var_export($param, TRUE)) . ');'
            . 'if(!is_array($res)) {echo $res;} else {extract($res);}';
        return "<" . '?php ' . $display_html . '?' . '>';
    }

    /**
     * 模板模块标签调用
     * @param $param
     * @return array
     */
    public static function tplTag_Module($param = []): array
    {
        try {
            $module = ucwords($param['module']);
            unset($param['module']);
            $action = ucwords($param['action']);
            unset($param['action']);
            if (isset($action) == FALSE || empty($action))
                $action = 'Main';
            $class = '\PKApp\\' . $module . '\Classes\\' . $module . 'TemplateTag';
            if (!class_exists($class)) {
                $class = '\PKExtApp\\' . $module . '\Classes\\' . $module . 'TemplateTag';
                class_exists($class) ?: out()->notice('no exists class: ' . $class);
            }
            $classObj = new $class();
            if (method_exists($classObj, $action)) {
                $res = $classObj->{$action}($param);
                if (!is_array($res)) {
                    throw new \Exception(str_replace(
                        ['[module]', '[action]'],
                        [$module, $action],
                        language('Template_TagFunc_Error')
                    ));
                }
                return $res;
            } else {
                throw new \Exception(language('Template_TagFunc_NoExists') . $module . ' / ' . $action);
            }
        } catch (\Exception $ex) {
            out()->notice($ex->getMessage());
        }
        return [];
    }

    /**
     * PK标签结束
     */
    static private function end_pk_tag(): string
    {
        return '';
    }

    /**
     * @return mixed
     */
    public function getIsAdmin()
    {
        return $this->_is_admin;
    }

}
